package com.doctcloud.common.redis.configure;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONReader;
import com.alibaba.fastjson2.JSONWriter;
import com.alibaba.fastjson2.filter.Filter;
import com.doctcloud.common.core.constant.Constants;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.buffer.ByteBufInputStream;
import io.netty.buffer.ByteBufOutputStream;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.client.codec.Codec;
import org.redisson.client.protocol.Decoder;
import org.redisson.client.protocol.Encoder;
import org.redisson.config.Config;
import org.redisson.spring.data.connection.RedissonConnectionFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.nio.charset.Charset;
import java.time.Duration;

/**
 * redis配置
 * 
 * @author  doctCloud samula
 */
@Configuration
@EnableCaching
@AutoConfigureBefore(RedisAutoConfiguration.class)
public class RedisConfig extends CachingConfigurerSupport {

    @Value("${spring.redis.host:localhost}")
    private String host;

    @Value("${spring.redis.port:6379}")
    private int port;

    @Value("${spring.redis.password:}")
    private String password;

    @Value("${spring.redis.database:0}")
    private int database;

    @Value("${spring.redis.timeout:3000}")
    private int timeout;

    /**
     * 配置Redisson客户端，并强制其使用Fastjson2进行编码。
     */
    /**
     * 配置Redisson客户端
     * 如果你需要使用Redisson的高级功能（如分布式锁），可以注入这个Bean
     */
    @Bean(destroyMethod = "shutdown")
    public RedissonClient redissonClient() {
        Config config = new Config();

        // 单机模式配置
        config.useSingleServer()
                .setAddress("redis://" + host + ":" + port)
                .setPassword(password.isEmpty() ? null : password)
                .setDatabase(database)
                .setConnectTimeout(timeout)
                .setConnectionPoolSize(64)
                .setConnectionMinimumIdleSize(10);

        // 【核心修改】
        // 实现 Redisson 的 Codec 接口，自定义序列化/反序列化逻辑
        config.setCodec(new Codec() {
            private final Charset charset = Charset.forName("UTF-8");
            private final Filter autoTypeFilter = JSONReader.autoTypeFilter(Constants.JSON_WHITELIST_STR);

            // --- 核心序列化/反序列化逻辑 ---
            @Override
            public Decoder<Object> getValueDecoder() {
                return (buf, state) -> {
                    if (buf.readableBytes() == 0) {
                        return null;
                    }
                    try (ByteBufInputStream is = new ByteBufInputStream(buf)) {
                        byte[] bytes = new byte[is.available()];
                        is.read(bytes);
                        return JSON.parseObject(bytes, Object.class, autoTypeFilter);
                    } catch (IOException e) {
                        throw new RuntimeException("Fastjson2 decode error", e);
                    }
                };
            }

            @Override
            public Encoder getValueEncoder() {
                return in -> {
                    if (in == null) {
                        return ByteBufAllocator.DEFAULT.buffer(0);
                    }
                    ByteBuf out = ByteBufAllocator.DEFAULT.buffer();
                    try (ByteBufOutputStream os = new ByteBufOutputStream(out)) {
                        JSON.writeTo(os, in, JSONWriter.Feature.WriteClassName);
                        return os.buffer();
                    } catch (IOException e) {
                        out.release();
                        throw new RuntimeException("Fastjson2 encode error", e);
                    }
                };
            }

            // --- Map 相关的序列化/反序列化，直接复用上面的实现 ---
            @Override
            public Decoder<Object> getMapValueDecoder() {
                return getValueDecoder();
            }

            @Override
            public Encoder getMapValueEncoder() {
                return getValueEncoder();
            }

            @Override
            public Decoder<Object> getMapKeyDecoder() {
                return getValueDecoder();
            }

            @Override
            public Encoder getMapKeyEncoder() {
                return getValueEncoder();
            }

            // --- 其他必要的接口方法 ---
            @Override
            public ClassLoader getClassLoader() {
                return getClass().getClassLoader();
            }
        });

        return Redisson.create(config);
    }

    /**
     * 使用Redisson的连接工厂替换默认工厂
     */
    @Bean
    public RedisConnectionFactory redisConnectionFactory(RedissonClient redissonClient) {
        return new RedissonConnectionFactory(redissonClient);
    }

    /**
     * 自定义RedisTemplate（使用方案一的Fastjson2序列化器）
     */
    @Bean
    @SuppressWarnings(value = {"unchecked", "rawtypes"})
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
        RedisTemplate<Object, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(connectionFactory);

        // 使用你确认过的、能正常工作的Fastjson2序列化器
        FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class);

        // Key使用String序列化
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(serializer);

        // Hash键值序列化
        template.setHashKeySerializer(new StringRedisSerializer());
        template.setHashValueSerializer(serializer);

        template.afterPropertiesSet();
        return template;
    }

    /**
     * 1分钟过期的缓存管理器
     */
    @Bean("cacheManager1Minute")
    public RedisCacheManager cacheManager1Minute(RedisConnectionFactory connectionFactory) {
        return RedisCacheManager.builder(connectionFactory)
                .cacheDefaults(instanceConfig(60L))
                .transactionAware()
                .build();
    }

    /**
     * 10分钟过期的缓存管理器
     */
    @Bean("cacheManager10Minute")
    public RedisCacheManager cacheManager10Minute(RedisConnectionFactory connectionFactory) {
        return RedisCacheManager.builder(connectionFactory)
                .cacheDefaults(instanceConfig(10 * 60L))
                .transactionAware()
                .build();
    }

    /**
     * 默认1小时过期的缓存管理器（Primary 表示默认使用）
     */
    @Bean("cacheManager1Hour")
    @Primary
    public RedisCacheManager cacheManager1Hour(RedisConnectionFactory connectionFactory) {
        return RedisCacheManager.builder(connectionFactory)
                .cacheDefaults(instanceConfig(3600L))
                .transactionAware()
                .build();
    }

    /**
     * 1天过期的缓存管理器
     */
    @Bean("cacheManager1Day")
    public RedisCacheManager cacheManager1Day(RedisConnectionFactory connectionFactory) {
        return RedisCacheManager.builder(connectionFactory)
                .cacheDefaults(instanceConfig(3600 * 24L))
                .transactionAware()
                .build();
    }

    /**
     * 统一配置 Jackson ObjectMapper
     */
    private ObjectMapper getObjectMapper() {
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
        objectMapper.registerModule(new JavaTimeModule());
        objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
        objectMapper.activateDefaultTyping(
                LaissezFaireSubTypeValidator.instance,
                ObjectMapper.DefaultTyping.NON_FINAL,
                JsonTypeInfo.As.PROPERTY
        );
        return objectMapper;
    }

    /**
     * 构建 RedisCacheConfiguration（统一设置过期时间和序列化方式）
     */
    private RedisCacheConfiguration instanceConfig(Long ttl) {
        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
        jackson2JsonRedisSerializer.setObjectMapper(getObjectMapper());

        return RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofSeconds(ttl))
                .disableCachingNullValues()
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer));
    }

    /**
     * 自定义缓存 Key 生成器（类名_方法名_参数）
     * 这个Bean会被@Cacheable的keyGenerator属性引用
     */
    @Bean("springCacheCustomKeyGenerator")
    @Override
    public KeyGenerator keyGenerator() {
        return (target, method, params) -> {
            StringBuilder key = new StringBuilder();
            key.append(target.getClass().getSimpleName())
                    .append("_")
                    .append(method.getName());
            if (params != null && params.length > 0) {
                key.append("_")
                        .append(StringUtils.arrayToDelimitedString(params, "_"));
            }
            return key.toString();
        };
    }
}